package com.ajun.es.query;

import com.ajun.common.ESConstants;
import com.ajun.common.util.EsClientFactory;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.FuzzyQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.metrics.Max;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.annotation.Testable;

import java.io.IOException;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

/**
 * ES - 查询
 *
 * @author AJun
 * @date 2021-07-21
 */
@Testable
@Slf4j
public class QueryTutorial {

    RestHighLevelClient client;
    final String USER_INDEX = ESConstants.USER_INDEX;

    @BeforeEach
    void init() {
        client = EsClientFactory.esClient();
    }

    @AfterEach
    void destroy() {
        EsClientFactory.close();
    }

    /**
     * 全量查询
     */
    @Test
    void testAllQuery() throws IOException {
        // 全量查询构造
        MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();

        // 查询
        SearchRequest request = new SearchRequest()
            .indices(USER_INDEX)
            .source(SearchSourceBuilder.searchSource().query(queryBuilder));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        log.debug("耗时: {}", response.getTook());
        SearchHits hits = response.getHits();
        log.debug("命中数量: {}", hits.getTotalHits());
        hits.forEach(hit -> log.debug("查询结果: {}", hit.getSourceAsString()));
    }

    /**
     * 条件查询
     */
    @Test
    void testCriteriaQuery() throws IOException {
        // 条件查询构造
        TermQueryBuilder queryBuilder = QueryBuilders.termQuery("age", 18);

        // 查询
        SearchRequest request = new SearchRequest()
            .indices(USER_INDEX)
            .source(SearchSourceBuilder.searchSource().query(queryBuilder));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        log.debug("耗时: {}", response.getTook());
        SearchHits hits = response.getHits();
        log.debug("命中数量: {}", hits.getTotalHits());
        hits.forEach(hit -> log.debug("查询结果: {}", hit.getSourceAsString()));
    }

    /**
     * 分页查询 & 排序 & 过滤字段
     */
    @Test
    void testPagingQuery() throws IOException {
        // 分页查询构造
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
        SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource().query(matchAllQueryBuilder);
        // 设置分页起止: (当前页 - 1) * 每页条数
        searchSourceBuilder.from(3);
        searchSourceBuilder.size(3);
        // 排序
        searchSourceBuilder.sort("age", SortOrder.DESC);
        // 过滤字段
        String[] includes = {}; // 包含
        String[] excludes = {"age"}; // 排除
        searchSourceBuilder.fetchSource(includes, excludes);

        // 查询
        SearchRequest request = new SearchRequest()
            .indices(USER_INDEX)
            .source(searchSourceBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        log.debug("耗时: {}", response.getTook());
        SearchHits hits = response.getHits();
        log.debug("命中数量: {}", hits.getTotalHits());
        hits.forEach(hit -> log.debug("查询结果: {}", hit.getSourceAsString()));
    }

    /**
     * 组合查询(多条件查询) & 范围查询
     */
    @Test
    void testMultipleConditionsQuery() throws IOException {
        // 组合查询构造
        BoolQueryBuilder boolQueryBuilder = QueryBuilders
            .boolQuery()
            .mustNot(QueryBuilders.matchQuery("age", 18)) // 必须不是18岁的
            .must(QueryBuilders.matchQuery("sex", "男")) // 必须是男性
            .filter(QueryBuilders.rangeQuery("age").gt(10)); // 过滤条件:大于10岁
        SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource().query(boolQueryBuilder);

        // 查询
        SearchRequest request = new SearchRequest()
            .indices(USER_INDEX)
            .source(searchSourceBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        log.debug("耗时: {}", response.getTook());
        SearchHits hits = response.getHits();
        log.debug("命中数量: {}", hits.getTotalHits());
        hits.forEach(hit -> log.debug("查询结果: {}", hit.getSourceAsString()));
    }

    /**
     * 全文检索 模糊查询
     */
    @Test
    void testFuzzyQuery() throws IOException {
        // 模糊查询构造
        FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "李").fuzziness(Fuzziness.AUTO);
        SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource().query(fuzzyQueryBuilder);

        // 查询
        SearchRequest request = new SearchRequest()
            .indices(USER_INDEX)
            .source(searchSourceBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        log.debug("耗时: {}", response.getTook());
        SearchHits hits = response.getHits();
        log.debug("命中数量: {}", hits.getTotalHits());
        hits.forEach(hit -> log.debug("查询结果: {}", hit.getSourceAsString()));
    }

    /**
     * 高亮查询
     */
    @Test
    void testHighLightQuery() throws IOException {
        SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource()
            // 高亮查询构造
            .highlighter(new HighlightBuilder().preTags("<font color='red'>").postTags("</font>").field("name"))
            .query(QueryBuilders.matchAllQuery());

        // 查询
        SearchRequest request = new SearchRequest()
            .indices(USER_INDEX)
            .source(searchSourceBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        log.debug("耗时: {}", response.getTook());
        SearchHits hits = response.getHits();
        log.debug("命中数量: {}", hits.getTotalHits());
        hits.forEach(hit -> log.debug("查询结果: {}", hit.getSourceAsString()));
    }

    /**
     * 聚合查询
     */
    @Test
    void testAggregationQuery() throws IOException {
        // 聚合查询构造
        SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource()
            .aggregation(AggregationBuilders.max("maxAge").field("age"));

        // 查询
        SearchRequest request = new SearchRequest()
            .indices(USER_INDEX)
            .source(searchSourceBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        log.debug("耗时: {}", response.getTook());
        SearchHits hits = response.getHits();
        log.debug("命中数量: {}", hits.getTotalHits());
        hits.forEach(hit -> log.debug("查询结果: {}", hit.getSourceAsString()));

        // 获取聚合结果
        Max maxAge = response.getAggregations().get("maxAge");
        log.debug("聚合结果,最大年: {}", maxAge.getValue());
    }

    /**
     * 聚合查询 - 分组查询
     */
    @Test
    void testGroupQuery() throws IOException {
        // 聚合分组查询构造
        SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource()
            .aggregation(AggregationBuilders.terms("ageGroup").field("age"));

        // 查询
        SearchRequest request = new SearchRequest()
            .indices(USER_INDEX)
            .source(searchSourceBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        log.debug("耗时: {}", response.getTook());
        SearchHits hits = response.getHits();
        log.debug("命中数量: {}", hits.getTotalHits());
        hits.forEach(hit -> log.debug("查询结果: {}", hit.getSourceAsString()));
    }

    @Test
    void countdown() {
        LocalTime timeOfGoHome = LocalTime.of(18, 0, 0);
        LocalTime timeOfNow = LocalTime.now();
        long secondsRemaining = timeOfNow.until(timeOfGoHome, ChronoUnit.SECONDS);
        for (long second = secondsRemaining; second >= 0; second--) {
            System.out.println(second + " seconds to go home.");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
